package org.stdoutremover;

import java.io.PrintWriter;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.util.CheckClassAdapter;
import org.objectweb.asm.util.TraceClassVisitor;

/**
 * 
 * Removes System.out.println()s such as:
 * 
 * String abc = "test"; System.out.println("Hello World3 " + abc + 2);
 * 
 * which results to:
 * 
 * L3 LINENUMBER 16 L3 LDC "test" ASTORE 1 L4 LINENUMBER 19 L4 GETSTATIC
 * java/lang/System.out : Ljava/io/PrintStream; NEW java/lang/StringBuilder DUP
 * LDC "Hello World3 " INVOKESPECIAL
 * java/lang/StringBuilder.<init>(Ljava/lang/String;)V ALOAD 1 INVOKEVIRTUAL
 * java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
 * ICONST_2 INVOKEVIRTUAL
 * java/lang/StringBuilder.append(I)Ljava/lang/StringBuilder; INVOKEVIRTUAL
 * java/lang/StringBuilder.toString()Ljava/lang/String; INVOKEVIRTUAL
 * java/io/PrintStream.println(Ljava/lang/String;)V
 * 
 * 
 * http://asm.ow2.org/doc/tutorial-asm-2.0.html
 * http://andrei.gmxhome.de/bytecode/index.html
 * http://download.forge.objectweb.org/asm/asm4-guide.pdf
 * 
 * http://javahowto.blogspot.de/2006/07/javaagent-option.html
 * http://docs.oracle.
 * com/javase/6/docs/api/java/lang/instrument/package-summary.html
 * 
 * @author bfriedrich
 * 
 */
public class StdoutRemovalAgent {

	// Javaagent implementation method
	public static void premain(final String agentArgs, final Instrumentation inst) 
	{
		inst.addTransformer(new ClassFileTransformer() 
		{
			public byte[] transform(ClassLoader l, String name, Class<?> c, ProtectionDomain d, byte[] b) throws IllegalClassFormatException 
			{
				final ClassReader 		cr 			  = new ClassReader(b);
				final ClassWriter 		cw 			  = new ClassWriter(ClassWriter.COMPUTE_MAXS);
				final CheckClassAdapter checkClassVis = new CheckClassAdapter(cw);
				final ClassNode 		cn 			  = new ClassNode();
				cr.accept(cn, ClassReader.SKIP_DEBUG);
				
				final StdoutRemovalTransformer transformer = new StdoutRemovalTransformer();
				transformer.transform(cn);
				cn.accept(checkClassVis);

				return cw.toByteArray();
			}
		});
	}

	
	/**
	 * just to play..
	 * 
	 * @param args
	 */
	public static void main(final String[] args) 
	{
		try 
		{
			ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);

			ClassReader classReader = new ClassReader("org.stdoutremover.HelloWorld");
			final CheckClassAdapter checkClassVis = new CheckClassAdapter(
					classWriter);
			final TraceClassVisitor traceVisitor = new TraceClassVisitor(
					checkClassVis, new PrintWriter(System.out));
			
			
			final ClassNode cn = new ClassNode();
			classReader.accept(cn, ClassReader.SKIP_DEBUG);
			
			final StdoutRemovalTransformer transformer = new StdoutRemovalTransformer();
			transformer.transform(cn);
	
			cn.accept(traceVisitor);


			byte[] classAsBytes = classWriter.toByteArray();

			final MyClassLoader classLoader = new MyClassLoader();

			final Class<?> c = classLoader.defineClass("org.stdoutremover.HelloWorld",
					classAsBytes);

			System.out.println("WITH SYSOUT: ");
			HelloWorld.main(null);

			System.out.println("WITHOUT SYSOUT: ");
			Method meth = c.getMethod("main", String[].class);
			String[] params = null; // init params accordingly
			meth.invoke(null, (Object) params);

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * just to play
	 */
	public static final class MyClassLoader extends ClassLoader {
		public Class defineClass(String name, byte[] b) {
			return defineClass(name, b, 0, b.length);
		}
	}	
}
